/**
 * Copyright (C) 2007 Mustafa K. Isik - isik@acm.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

package com.paxcodi.threads.diner.gathering;

import java.util.ArrayList;
import java.util.List;

//TODO replace System.out calls with some logging mechanism

public class CircularTable {

	private static final CircularTable INSTANCE = new CircularTable();

	private List<Chopstick> chopsticks;
	private List<Seat> seats;

	private int numberOfSeats;


	/**
	 * Private constructor ensures non-instantiability via external calls.
	 */
	private CircularTable(){
		chopsticks = new ArrayList<Chopstick>();
		seats = new ArrayList<Seat>();
	}

	private void setNumberOfSeats(int numberOfSeats){
		this.numberOfSeats = numberOfSeats;
	}

	/**
	 * Enforces singleton property of the <code>CircularTable</code> class.
	 * @return only instance of this class
	 */
	public static CircularTable getInstance(){
		return INSTANCE;
	}

	/**
	 * The table is served with as many chopsticks as there are seats.
	 *
	 */
	private void serveTable() {
		for(int j = 0; j < numberOfSeats; j++){
			//indexpositions of sticks and seats are of prime importance
			chopsticks.add(new Chopstick(j));
			seats.add(new Seat(j));
		}
	}

	/**
	 * Seats the caller on a free seat.
	 * 
	 * @return index of seat on which
	 */
	Seat requestToBeSeated() {

		boolean foundFreeSeat = false;
		Seat foundSeat = null;

		System.out.println("P " + Thread.currentThread().getName() + 
		" requests to be seated.");

		synchronized(seats) { //explicit synching on Collection
			//search for unoccupied seat and return its index to caller
			while(!foundFreeSeat) {

				for(Seat currentSeat : seats) {

					if(!currentSeat.isOccupied()) {

						currentSeat.setOccupied(true);
						//foundSeatIndex = currentSeat.getSeatIndex();
						foundFreeSeat = true;
						foundSeat = currentSeat;
						System.out.println("P " + 
								Thread.currentThread().getName() + 
								" has found a free seat at index " + 
								foundSeat.getSeatIndex() + ".");
						break;
					} //if

				}//for 

				if(!foundFreeSeat) {
					System.out.println("P " + 
							Thread.currentThread().getName() + 
					" is waiting for a free seat.\n");
					try {
						seats.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			} //while
		}//synchronized block

		return foundSeat;
	}

	void releaseSeat(Seat toBeFreed) {
		synchronized(seats){
			System.out.println("P " + Thread.currentThread().getName() + 
					" gets up from seat " + toBeFreed.getSeatIndex() + ".");
			toBeFreed.setOccupied(false);
			seats.notifyAll();
		}
	}

	void requestChopsticksForSeat(int requestingSeatIndex){
		System.out.println("P " + Thread.currentThread().getName() + 
		" sits hungry at table.");
		synchronized(chopsticks){//new syncing mechanism
			Chopstick leftStick = chopsticks.get(requestingSeatIndex);
			Chopstick rightStick = chopsticks.get(
					(requestingSeatIndex + 1) % numberOfSeats);
			//this condition needs to be checked in any case, even when
			//having been awakened while waiting - wakeups can even
			//occur for reasons beyond a call to notify(), e.g. due to the VM
			//performing a spontaneous wakeup
			while(leftStick.isBeingUsed() || rightStick.isBeingUsed()){
				System.out.println("P " + Thread.currentThread().getName() + 
				" is waiting for sticks.\n");
				try {
					//attention: give up lock on chopsticks-List-object 
					//if you cannot acquire single Chopstick-objects in 
					//the way intended!
					chopsticks.wait();
					//start competing for lock again, once awakened by a call to
					//the chopsticks-List-object's notify()
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("P " + Thread.currentThread().getName() + 
					" picks up sticks " + leftStick.getIndex() + " and " + 
					rightStick.getIndex() + ".");
			leftStick.setInUse(true);
			rightStick.setInUse(true);
			System.out.println("Sticks " + leftStick.getIndex() + " and " + 
					rightStick.getIndex() + " are in use by P " + 
					Thread.currentThread().getName() + ".");
		}
	}

	void releaseChopsticksForSeat(int releasingSeatIndex){
		synchronized(chopsticks){
			Chopstick leftStick = chopsticks.get(releasingSeatIndex);
			Chopstick rightStick = chopsticks.get(
					(releasingSeatIndex + 1) % numberOfSeats);
			System.out.println("\nPhilosopher number " + 
					Thread.currentThread().getName() + 
			" puts down chopsticks.");
			leftStick.setInUse(false);
			rightStick.setInUse(false);

			chopsticks.notifyAll();
		}
	}


	/**
	 * 
	 * @param args seats philosophers valid-hungry-philosopher-id
	 */
	public static void main(String args[]){
		int inputNumberOfSeats = Integer.parseInt(args[0]);
		int inputNumberOfPhilosophers = Integer.parseInt(args[1]);
		int inputHungryPhilosopherId = Integer.parseInt(args[2]);

		CircularTable.getInstance().setNumberOfSeats(inputNumberOfSeats);
		CircularTable.getInstance().serveTable();

		for(int i = 0; i < inputNumberOfPhilosophers; i++){
			if (!(inputHungryPhilosopherId == i))
				new Thread(new Philosopher(1000), Integer.toString(i)).start();
			else
				new Thread(new Philosopher(500), Integer.toString(i)).start();
		}
	}

}
